package main

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/charmbracelet/bubbles/spinner"
	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/spf13/viper"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

type (
	errMsg error
	model  struct {
		choice     string
		gitCmd     string
		pushResult string
		ver        int
		cursor     int
		step       int
		err        error
		textInput  textinput.Model
		spinner    spinner.Model
		loading    bool
	}
	Config struct {
		GitUrl     string
		Tag        string
		Comment    string
		VerPrefix  string
		AutoCommit int
	}
	gitMessage struct {
		err    error
		ver    int
		gitCmd string
	}
	pushMessage struct {
		err error
		res string
	}
)

var (
	config       = Config{}
	choices      = []string{"预发", "正式"}
	envPrefix    string
	tagV         string
	successStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#67c23a"))
	errorStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("#f56c6c"))
	proColor     = lipgloss.NewStyle().Foreground(lipgloss.Color("#daa520"))
	helpRender   = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render
	msgChan      = make(chan tea.Cmd)
)

func w(text string) {
	file, err := os.OpenFile("tagV.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("无法打开文件:", err)
		return
	}
	defer file.Close()
	_, err = file.WriteString(text + "\n")
	if err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}
}

func (m model) execPush() tea.Cmd {
	cmdName := "git"
	r := regexp.MustCompile(`git tag -a (.*?) -m (.*)`)
	s := m.gitCmd

	matches := r.FindAllStringSubmatch(s, -1)
	if !(len(matches) == 1 && len(matches[0]) == 3) {
		return func() tea.Msg {
			return pushMessage{err: errors.New("无效的git命令->" + s)}
		}
	}
	tagName := matches[0][1]
	comment := matches[0][2]
	s = strings.Replace(s, cmdName+" ", "", 1)
	s = strings.Replace(s, " "+comment, "", 1)
	args := strings.Split(s, " ")
	comment = strings.Replace(comment, "'", "", 2)
	args = append(args, comment)
	cmd := exec.Command(cmdName, args...)
	bErr := strings.Builder{}
	cmd.Stderr = &bErr
	e := cmd.Run()
	if e != nil {
		return func() tea.Msg {
			return pushMessage{err: errors.New(bErr.String())}
		}
	}
	cmd = exec.Command("git", "push", "origin", tagName)
	var bOut []byte
	bOut, _ = cmd.CombinedOutput()
	return func() tea.Msg {
		return pushMessage{res: string(bOut)}
	}
}

func (m model) getGitTagsCmd() tea.Cmd {
	envIdx := m.cursor
	envPrefix = ""
	if envIdx == 0 {
		envPrefix = "pre_"
	}
	args := []string{"ls-remote", "--tags"}
	if config.GitUrl != "" {
		args = append(args, config.GitUrl)
	}
	// 创建命令对象
	cmd := exec.Command("git", args...)
	// 执行命令并获取输出
	output, err := cmd.Output()
	if err != nil {
		return func() tea.Msg {
			return gitMessage{err: err}
		}
	}
	bb := bytes.Split(output, []byte("\n"))
	ver := 0
	for _, b := range bb {
		arr := bytes.Split(b, []byte("\t"))
		if len(arr) == 2 {
			bv := bytes.Replace(arr[1], []byte("refs/tags/"+envPrefix+tagV), []byte(""), 1)
			iv, e := strconv.Atoi(string(bv))
			if e != nil {
				continue
			}
			if iv > ver && iv < 40000 {
				ver = iv
			}
		}
	}

	w("ver=" + strconv.Itoa(ver))
	if ver > 0 {
		ver++
		sVer := strconv.Itoa(ver)
		sVer = strings.Replace(sVer, "4", "5", -1)
		sVer = strings.Replace(sVer, "13", "15", -1)
		ver, _ = strconv.Atoi(sVer)
		m.ver = ver
		m.gitCmd = fmt.Sprintf("git tag -a %s%s%d -m '%s'", envPrefix, tagV, ver, m.textInput.Value())
		w(m.gitCmd)
	}

	return func() tea.Msg {
		return gitMessage{ver: ver, gitCmd: m.gitCmd}
	}
}

func initialModel() model {
	ti := textinput.New()
	ti.SetValue("zdgl ")
	ti.Placeholder = "zdgl "
	ti.Focus()
	ti.CharLimit = 156
	ti.Width = 20
	s := spinner.New()
	s.Spinner = spinner.Points
	s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
	return model{
		textInput: ti,
		err:       nil,
		spinner:   s,
	}
}

func (m model) Init() tea.Cmd {
	return textinput.Blink
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmd tea.Cmd
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "q", "esc":
			if m.step == 1 {
				m.step = 0
			} else {
				return m, tea.Quit
			}
		case "ctrl+c":
			return m, tea.Quit
		case "enter":
			if m.step == 1 {
				m.loading = true
				go func() {
					msgChan <- m.execPush()
				}()
				return m, m.spinner.Tick
			} else {
				text := m.textInput.Value()
				if text == "" {
					m.err = errors.New("请输入注释")
				} else if strings.Index(text, "zdgl") != 0 {
					m.err = errors.New("缺少zdgl")
				} else if m.loading == false {
					m.loading = true
					go func() {
						msgChan <- m.getGitTagsCmd()
					}()
					return m, m.spinner.Tick
				}
			}
		case "down":
			m.cursor++
			if m.cursor >= len(choices) {
				m.cursor = 0
			}
		case "up":
			m.cursor--
			if m.cursor < 0 {
				m.cursor = len(choices) - 1
			}
		default:
			m.err = nil
		}
	case spinner.TickMsg:
		select {
		case cmd = <-msgChan:
			return m, cmd
		default:
			m.spinner, cmd = m.spinner.Update(msg)
		}
		return m, cmd
	case gitMessage:
		m.err = msg.err
		m.ver = msg.ver
		if config.AutoCommit == 1 {
			m.gitCmd = msg.gitCmd
			m.step = 2
			go func() {
				msgChan <- m.execPush()
			}()
			return m, m.spinner.Tick
		} else {
			m.loading = false
			m.step = 1
		}
	case pushMessage:
		m.pushResult = msg.res
		m.err = msg.err
		m.loading = false
	case errMsg:
		m.err = msg
		return m, nil
	}
	if m.step == 0 && m.loading == false {
		m.textInput, cmd = m.textInput.Update(msg)
	}
	return m, cmd
}

func (m model) View() string {
	s := strings.Builder{}
	headerString := ""
	if config.VerPrefix != "" {
		headerString = proColor.Render(config.VerPrefix + " ")
	}
	if m.step >= 1 {
		s.WriteString(headerString + helpRender("* 提示：(esc 返回 | 回车提交)") + "\n")
		s.WriteString(fmt.Sprintf("最新版本号：%d\n", m.ver))
		s.WriteString(successStyle.Render(m.gitCmd) + "\n")
		if m.loading {
			s.WriteString(m.spinner.View() + "\n")
		}
		if m.pushResult != "" {
			s.WriteString(m.pushResult)
		}
		if m.err != nil {
			s.WriteString(errorStyle.Render(m.err.Error()))
		}
	} else {
		s.WriteString(headerString + helpRender("* 提示：(esc 退出 | ↑↓选择环境 | 回车获取版本号)") + "\n")
		s.WriteString("选择环境：\n")
		for i := 0; i < len(choices); i++ {
			if m.cursor == i {
				s.WriteString("(" + successStyle.Render("•") + ") ")
			} else {
				s.WriteString("( ) ")
			}
			s.WriteString(choices[i])
			s.WriteString("\n")
		}
		s.WriteString("注释：\n")
		s.WriteString(m.textInput.View())
		if m.loading {
			s.WriteString("\n" + m.spinner.View())
		}
		tip := ""
		if m.err != nil {
			tip = m.err.Error()
		}
		msg := ""
		if tip != "" {
			msg = " * " + tip
			s.WriteString(errorStyle.Render(msg))
		}
	}
	return s.String()
}

func loadConfig() {
	configName := "tagv"
	configType := "toml"
	configPath := fmt.Sprintf("%s.%s", configName, configType)
	_, err := os.Stat(configPath)
	if os.IsNotExist(err) {
		e := os.WriteFile("tagv.toml", []byte(`GitUrl = "" // 选填
Tag = ""`), 0644)
		if e != nil {
			log.Fatal("自动创建配置文件失败：\n", e)
		} else {
			log.Fatalf("缺少配置文件%s，已自动生成，请补充配置文件'./tagv.toml'\n", configPath)
		}
	}
	viper.SetConfigName(configName)
	viper.SetConfigType(configType)
	viper.AddConfigPath(".")
	// 读取配置文件
	err = viper.ReadInConfig()
	if err != nil {
		log.Fatal("Error reading config file:", err)
		return
	}
	err = viper.Unmarshal(&config)
	if err != nil {
		log.Fatal("Error unmarshaling config:", err)
		return
	}
	if config.Tag == "" {
		log.Fatal("无效的配置，请配置站点tag：pkht | zht | qt")
		return
	}
	tagV = config.Tag + "v"
}

func main() {
	loadConfig()
	m := initialModel()
	p := tea.NewProgram(m)
	if _, err := p.Run(); err != nil {
		log.Fatal(err)
	}
}
